Skip to content

Fix/wcn 196 eddsa recipient guard#8630

Open
mrdanish26 wants to merge 1 commit intomasterfrom
fix/WCN-196-eddsa-recipient-guard
Open

Fix/wcn 196 eddsa recipient guard#8630
mrdanish26 wants to merge 1 commit intomasterfrom
fix/WCN-196-eddsa-recipient-guard

Conversation

@mrdanish26
Copy link
Copy Markdown
Contributor

@mrdanish26 mrdanish26 commented Apr 24, 2026

Summary

Follow-up to WAL-375 (#8539). Applies the same recipient verification guard to the EdDSA TSS signing path.

The vulnerability (same root cause as WAL-375)

EddsaUtils.signRequestBase() did not call verifyTransaction with the actual recipients, allowing a compromised API server to substitute a malicious signableHex — the transaction presented to the user for signing could differ from what was actually broadcast.

Fix

Added resolveEffectiveTxParams + verifyTransaction call inside signRequestBase() for RequestType.tx, immediately after unsignedTx is resolved and before key derivation begins:

const effectiveTxParams = resolveEffectiveTxParams(txRequestResolved, params.txParams);
await this.baseCoin.verifyTransaction({
  txPrebuild: { txHex: unsignedTx.signableHex },
  txParams: effectiveTxParams,
  wallet: this.wallet,
  walletType: this.wallet.multisigType(),
});

EdDSA-specific NO_RECIPIENT_TX_TYPES expansion

EdDSA coins have many more transaction types that legitimately carry no recipients. The shared NO_RECIPIENT_TX_TYPES set in recipientUtils.ts was expanded from 8  44 types:

┌───────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
                   Category                                                                                                                              Types                                                                                                           
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 Staking (SOL, ADA, NEAR, DOT, CSPR, SUI, APT)  stakingActivate, stakingDeactivate, stakingWithdraw, stakingClaim, stakingDelegate, stakingUnlock, stakingUnvote, stakingPledge, stakingAuthorize, stakingAuthorizeRaw, stakingLock, stakingAdd, addStake, withdrawStake 
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 Account / wallet init                          walletInitialization, addressInitialization, createAccount, accountUpdate                                                                                                                                                
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 Token operations                               trustline, closeAssociatedTokenAccount, associatedTokenAccountInitialization, tokenTransfer, sendToken, sendNFT                                                                                                          
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 NEAR                                           storageDeposit                                                                                                                                                                                                           
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 TON                                            singleNominatorWithdraw, tonWhalesDeposit, tonWhalesWithdrawal, tonWhalesVestingDeposit, tonWhalesVestingWithdrawal                                                                                                      
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 ADA                                            voteDelegation                                                                                                                                                                                                           
├───────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
 Canton 2-step transfers                        transferAccept, transferReject, transferAcknowledge, transferOfferWithdrawn, oneStepPreApproval                                                                                                                          
└───────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

intent.intentType fallback

EdDSA coins populate txRequest.intent.intentType but often don't set txParams.type. The guard now falls back to intent.intentType when txParams.type is absent — this is required for EdDSA staking and wallet init flows to pass the guard without breaking.

Test plan

- recipientUtils unit tests  20 passing (ECDSA types, EdDSA staking types, Canton types, intentType fallback)
- EdDSA signTxRequest tests  9 passing (rejection for payment without recipients, acceptance for stakingActivate and walletInitialization)
- ECDSA MPCv2 signTxRequest tests  13 passing (no regression on shared recipientUtils.ts)

Known edge cases

- XLM: verifyTransaction makes 2 keychain API calls per sign  ~100–300ms latency, pre-existing behaviour not introduced here
- HBAR / DOT: strict recipient checks in verifyTransaction for some types  mitigated by comprehensive exempt list; raise a follow-up if new types surface

TICKET: WCN-196

@linear
Copy link
Copy Markdown

linear Bot commented Apr 24, 2026

Add resolveEffectiveTxParams guard to EdDSA signRequestBase() mirroring
the ECDSA fix from WAL-375. Expands NO_RECIPIENT_TX_TYPES with ~36 EdDSA
coin types (staking, wallet init, token ops, CANTON flows) that legitimately
carry no recipients. Guard also falls back to intent.intentType when
txParams.type is unset, covering EdDSA coins that populate intent but not
txParams.

TICKET: WCN-196
@mrdanish26 mrdanish26 force-pushed the fix/WCN-196-eddsa-recipient-guard branch from 5ccc429 to f45a0dd Compare April 27, 2026 18:59
@mrdanish26 mrdanish26 marked this pull request as ready for review April 27, 2026 21:08
@mrdanish26 mrdanish26 requested review from a team as code owners April 27, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant